package momo.multitree.visual;

import java.awt.Graphics2D;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import org.apache.batik.dom.GenericDOMImplementation;
import org.apache.batik.svggen.SVGGraphics2D;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

import momo.multitree.structure.Edge;
import momo.multitree.structure.Node;
import momo.multitree.structure.Tree;

/**
 * Renders a Tree Object into Graphics Object
 * 
 * Circle Diameter = 22
 * X distance between circle = 20
 * Y distance between level = 50  
 * 
 * Placement of single digits:
 * digit's x = circle's x + 8
 * digit's y = circle's y + 15
 * 
 * Placement of double digits:
 * digit's x = circle's x + 4
 * digit's y = circle's y + 15
 * 
 * Parent's x = Summation of child's x / no. of children
 *  
 * Placement of line:
 * First pair from parent:
 * x = parent's x + radius of circle
 * y = parent's y + diameter of circle
 *  
 * Second pair from child:
 * x = child's x + radius of circle
 * y = child's y
 * 
 * @author jacktty
 *
 */
public class TreeRenderOutput implements TreeVisualOutput
{
	static final int HORIZONTAL_VIEW = 0;
	static final int VERTICAL_VIEW = 1;
	private final int INIT_X = 50;
	private final int INIT_Y = 50;
	private final int CIRCLE_DIAMETER = 22;
	private final int WIDTH_APART = 50;
	private final int HEIGHT_APART = 100;
	private final int SINGLE_DIGIT_X_DISPLACEMENT = 8;
	private final int DOUBLE_DIGIT_X_DISPLACEMENT = 4;
	private final int DIGIT_Y_DISPLACEMENT = 15;
	private final float DEFAULT_FONT_SIZE = 12f;
	private final float LABEL_FONT_SIZE = 8f;
	
	private int childCount;
	
	private Tree tree;
	private boolean showCostLabel, showLatencyLabel;
	private boolean showWorkloadLabel, showStabilityLabel;
	private int view;
	
	public TreeRenderOutput()
	{
		childCount = 0;
		showCostLabel = true;
		showLatencyLabel = true;
		showWorkloadLabel = true;
		showStabilityLabel = true;
		view = HORIZONTAL_VIEW;
	}
	
	public void output(String filename) throws Exception
	{
		DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();

        // Create an instance of org.w3c.dom.Document.
        String svgNS = "http://www.w3.org/2000/svg";
        Document document = domImpl.createDocument(svgNS, "svg", null);

        // Create an instance of the SVG Generator.
        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);

        // Ask the test to render into the SVG Graphics2D implementation.
        this.renderTree(svgGenerator);

        // Finally, stream out SVG to the standard output using
        // UTF-8 encoding.
        boolean useCSS = false; // we want to use CSS style attributes
        Writer out = new OutputStreamWriter(new FileOutputStream("visual/TreeRender/"+filename+".svg"), "UTF-8");
        svgGenerator.stream(out, useCSS);
	}
	
	private void renderTree(Graphics2D g)
	{
		if ( tree != null )
		{
			childCount = 0;
			renderTree(g, tree, new Vector<Integer>(), 0, new HashSet<Node>(), tree.getRoot());
		}
	}
	
	private void renderTree(Graphics2D g, Tree tree, Vector<Integer> xCor, int level, HashSet<Node> visited, Node currNode)
	{
		Vector<Integer> myChildXCor = new Vector<Integer>();
		Vector<Edge> outEdge = new Vector<Edge>();
		visited.add(currNode);
		
		Set<Edge> set = tree.getEdgesOfNode(currNode);
		for(Edge edge: set)
		{
			Node toNode = edge.nextNode(currNode);
			if ( !visited.contains(toNode) )
			{
				renderTree(g, tree, myChildXCor, level+1, visited, toNode);
				outEdge.add(edge);
			}
		}
		
		//drawing starts here
		//Lets draw myself first
		int ovalX = 0;
		if ( myChildXCor.isEmpty() )
		{
			ovalX = (childCount*(WIDTH_APART+CIRCLE_DIAMETER)) + INIT_X;
			childCount ++;
		}else {
			int sum = 0;
			int size = myChildXCor.size();
			for(int x: myChildXCor)
				sum += x;
			ovalX = sum/size;
		}
		//add into vector for my parent usage
		xCor.add(ovalX);
		
		int ovalY = (level*HEIGHT_APART) + INIT_Y;
		
		if ( view == HORIZONTAL_VIEW )
		{
			int tmpX = ovalX;
			ovalX = ovalY;
			ovalY = tmpX;
		}
		
		int stringX = ovalX + getXForDigit(currNode.getId());
		int stringY = ovalY + DIGIT_Y_DISPLACEMENT;
		
		//draw the circle
		g.drawOval(ovalX, ovalY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
		//draw the node ID into the circle
		g.drawString(""+currNode.getId(), stringX, stringY);
		
		double workload = rdTo(currNode.getWorkloadRate(), 2);
		double stability = rdTo(currNode.getStability(), 2);
		
		// draw the node's workload and stability
		if ( view == HORIZONTAL_VIEW )
			renderNodeLabel(g, workload, stability, ovalX-5, ovalY-5);
		else
			renderNodeLabel(g, workload, stability, ovalX+CIRCLE_DIAMETER+5, ovalY+(CIRCLE_DIAMETER/2)+2);
		
		
		//draw lines connect to my children (if any)
		int parentX = 0;
		int parentY = 0;
		int childX = 0;
		int childY = 0;
		
		parentX = ovalX + (view==HORIZONTAL_VIEW?CIRCLE_DIAMETER:CIRCLE_DIAMETER/2);
		parentY = ovalY + (view==HORIZONTAL_VIEW?CIRCLE_DIAMETER/2:CIRCLE_DIAMETER);
		childX = view==HORIZONTAL_VIEW?ovalX + HEIGHT_APART:0;
		childY = view==HORIZONTAL_VIEW?0:ovalY + HEIGHT_APART;
		
		for(int i=0; i<myChildXCor.size(); i++)
		{
			Edge e = outEdge.get(i);
			
			double latency = rdTo(e.getLatency(currNode), 2);
			double cost = rdTo(e.getCost(currNode), 2);
			
			if ( view==HORIZONTAL_VIEW )
				childY = myChildXCor.get(i) + CIRCLE_DIAMETER/2;
			else
				childX = myChildXCor.get(i) + CIRCLE_DIAMETER/2;
			
			//draw edges connected from parent to child
			g.drawLine(parentX, parentY, childX, childY);
			
			//draw the edge's latency and cost
			if ( view==HORIZONTAL_VIEW )
			{
				int x = (parentX + childX) / 2;
				int y = (parentY + childY) / 2;
				double diff = (double)(parentY-childY)/(double)(parentX-childX);
				double rotAngle = Math.atan(diff) ;
				g.rotate(rotAngle, x, y);
				renderEdgeLabel(g, latency, cost, x-2, y-2);
				g.rotate(-rotAngle, x, y);
			}else
				renderEdgeLabel(g, latency, cost, myChildXCor.get(i), childY-2);
		}
	}
	
	private void renderNodeLabel(Graphics2D g, double workload, double stability, int x, int y)
	{
		g.setFont(g.getFont().deriveFont(LABEL_FONT_SIZE));
		
		if ( showWorkloadLabel && showStabilityLabel )
			g.drawString("["+workload+", "+stability+"]", x, y);
		else if ( showWorkloadLabel )
			g.drawString("["+workload+"]", x, y);
		else if ( showStabilityLabel )
			g.drawString("["+stability+"]", x, y);
		
		g.setFont(g.getFont().deriveFont(DEFAULT_FONT_SIZE));
	}
	
	private void renderEdgeLabel(Graphics2D g, double latency, double cost, int x, int y)
	{
		g.setFont(g.getFont().deriveFont(LABEL_FONT_SIZE));
		
		if ( showLatencyLabel && showCostLabel )
			g.drawString("("+latency+", "+cost+")", x, y);
		else if ( showLatencyLabel )
			g.drawString("("+latency+")", x, y);
		else if ( showCostLabel )
			g.drawString("("+cost+")", x, y);
		
		g.setFont(g.getFont().deriveFont(DEFAULT_FONT_SIZE));
	}
	
	//use this for now
	private int getXForDigit(String id)
	{
		if (  id.length() == 2 )
			return DOUBLE_DIGIT_X_DISPLACEMENT;
		else
			return SINGLE_DIGIT_X_DISPLACEMENT;
	}
	
	private double rdTo(double value, int decimalPlace)
	{
		double times = Math.pow(10, decimalPlace);
		double newVal = Math.round(value * times)/times;
		return newVal;
	}

	public void setTree(Tree t) { this.tree = t; }
	public void showNodeLabel(boolean show){ showWorkloadLabel = show; showStabilityLabel = show; }
	public void showEdgeLabel(boolean show){ showCostLabel = show; showLatencyLabel = show; }
	public void showCostLabel(boolean show){ showCostLabel = show; }
	public void showLatencyLabel(boolean show){ showLatencyLabel = show; }
	public void showWorkloadLabel(boolean show){ showWorkloadLabel = show; }
	public void showStabilityLabel(boolean show){ showStabilityLabel = show; }
	public void setView(int newView){ view = newView==HORIZONTAL_VIEW?HORIZONTAL_VIEW:VERTICAL_VIEW; }
	public boolean isShowCostLabel() { return showCostLabel; }
	public boolean isShowLatencyLabel() { return showLatencyLabel; }
	public boolean isShowWorkloadLabel() { return showWorkloadLabel; }
	public boolean isShowStabilituLabel() { return showStabilityLabel; }
	public int getView() { return view; }

}//end of class TreeRenderOutput